﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Globalization;
using System.Text;
using System.IO;
using System.Threading;
using System.Diagnostics;
using Resources = QuickTools.MatchmediaHelper.Properties.Resources;
using Settings = QuickTools.MatchmediaHelper.Properties.Settings;
using Wire;

namespace QuickTools.MatchmediaHelper
{
    public class ReplayWatcher
    {
        #region Events

        public event EventHandler<ReplayStatusChangedEventArgs> ReplayStatusChanged;

        #endregion

        #region Variables

        private string targetFullpath;
        //private FileSystemWatcher fileWatcher = new FileSystemWatcher();
        private List<FileSystemWatcher> fsWatchers = new List<FileSystemWatcher>();
        private Timer ReplayWriteTimeout = null;
        private Timer fileSystemRefreshTimer = null;
        private bool isReplayRecording = false;
        private StringCollection gatheredFiles = new StringCollection();
        private const NotifyFilters defaultNotifyFlags = NotifyFilters.LastWrite | NotifyFilters.Size | NotifyFilters.CreationTime | NotifyFilters.FileName;
        private GameInterface gameInterface;

        #endregion

        public ReplayWatcher(GameInterface gameInterface)
        {
            this.gameInterface = gameInterface;
            gameInterface.MatchStarted += new GameInterface.MatchStartedHandler(gameInterface_MatchStarted);
            // set up FileSystemWatcher
            //InitFileSystemWatcher(fileWatcher, ReplayWatcher_Changed);

            // set up timers
            ReplayWriteTimeout = new Timer(DemoTimerTick);
            fileSystemRefreshTimer = new Timer(RefreshTimerTick);
        }
        private int matchId = 0;
        void gameInterface_MatchStarted(int matchId, string matchMediaPath)
        {
            this.matchId = matchId;
        }

        [Conditional("DEBUG")]
        private void LogDebug(string format, params object[] args)
        {
            Debug.WriteLine(String.Format(format, args));
        }

        [Conditional("TRACE")]
        private void LogTrace(string format, params object[] args)
        {
            Trace.WriteLine(String.Format(format, args));
        }

        /// <summary>
        /// This functions is a workaround for a bug in FileSystemWatcher present from Vista upwards
        /// that causes it not to fire any events when files change.
        /// </summary>
        /// <param name="dummy">Not used</param>
        private void RefreshTimerTick(object dummy)
        {
            try
            {
                // make a copy for thread safety
                FileInfo tempCopy = recentFile;
                if (tempCopy != null)
                {
                    // we are already watching a specific file, only refresh that one
                    LogDebug("Refreshing '{0}'", tempCopy.FullName);
                    // make System.IO refresh file information
                    //tempCopy.Refresh();
                    using (FileStream fs = new FileStream(tempCopy.FullName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
                    {
                        fs.ReadByte();
                    }
                }
                else
                {
                    // we are not monitoring a specific file yet, refresh all (takes longer)
                    foreach (FileSystemWatcher fileWatcher in fsWatchers)
                    {
                        string[] files = Directory.GetFiles(fileWatcher.Path, fileWatcher.Filter);
                        LogDebug("Refreshing {0} files", files.Length);
                        foreach (string f in files)
                        {
                            using (FileStream fs = new FileStream(f, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
                            {
                                fs.ReadByte();
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Trace.WriteLine(ex.ToString());
            }
        }

        private void DemoTimerTick(object dummy)
        {
            if (!isReplayRecording)
            {
                LogDebug("No Demo recording!");
                EventHandler<ReplayStatusChangedEventArgs> h = ReplayStatusChanged;
                if (h != null)
                {
                    h(this, new ReplayStatusChangedEventArgs(false));
                }
            }
            isReplayRecording = false;
        }

        private static void InitFileSystemWatcher(FileSystemWatcher watcher, FileSystemEventHandler handler)
        {
            watcher.InternalBufferSize = 32768;
            watcher.Changed += handler;
            watcher.Created += handler;
            watcher.NotifyFilter = defaultNotifyFlags;
        }

        private void StartWatcher(FileSystemWatcher watcher, string path, string filter)
        {
            watcher.Filter = filter;
            watcher.Path = path;
            watcher.IncludeSubdirectories = Properties.Settings.Default.MonitorSubdirectories;
            watcher.EnableRaisingEvents = true;
        }

        public void Start(string filter, params string[] directories)
        {
            if (directories == null)
                throw new ArgumentNullException("directory");
            if (directories.Length == 0)
                throw new ArgumentException("No directories given", "directory");
            isReplayRecording = false;
            foreach (string directory in directories)
            {
                LogTrace("Start monitoring '{0}' with filter '{1}'", directory, filter);
                try
                {
                    FileSystemWatcher w = new FileSystemWatcher();
                    fsWatchers.Add(w);
                    InitFileSystemWatcher(w, ReplayWatcher_Changed);
                    StartWatcher(w, directory, filter);
                }
                catch (Exception ex)
                {
                    Trace.WriteLine(ex.ToString());
                }
            }
            ReplayWriteTimeout.Change(Settings.Default.ReplayWriteTimeout, Settings.Default.ReplayWriteTimeout);
            int refreshInterval = Properties.Settings.Default.ReplayWriteTimeout / 2;
            fileSystemRefreshTimer.Change(refreshInterval, refreshInterval);
        }

        public void Stop()
        {
            foreach (FileSystemWatcher w in fsWatchers)
            {
                w.EnableRaisingEvents = false;
                w.Changed -= ReplayWatcher_Changed;
                w.Created -= ReplayWatcher_Changed;
                w.Dispose();
            }
            fsWatchers.Clear();
            
            ReplayWriteTimeout.Change(Timeout.Infinite, Timeout.Infinite);
            fileSystemRefreshTimer.Change(Timeout.Infinite, Timeout.Infinite);
            isReplayRecording = false;
            matchId = 0;
            LogTrace("Stopped monitoring");
        }

        private FileInfo recentFile = null;

        private string GetDefaultFilenameForFile(string fullPath)
        {
            // generate date string in filename-friendly format
            string dateString = DateTime.Now.ToString("yy-MM-dd_HH-mm-ss", DateTimeFormatInfo.InvariantInfo);
            // construct complete filename
            return String.Format(
                "{0}_{1}{2}",
                Path.GetFileNameWithoutExtension(fullPath),
                dateString,
                Path.GetExtension(fullPath)
                );
        }

        private void OnFileWritten(string fullPath)
        {
            EventHandler<ReplayStatusChangedEventArgs> h = ReplayStatusChanged;
            if (h != null)
            {

                var eventArgs = new ReplayStatusChangedEventArgs(true, fullPath);
                // call listeners
                h(this, eventArgs);
                // retrieve destination listener(s) might have set
                targetFullpath = eventArgs.MoveToDestination;
            }
            else
                targetFullpath = null;

            // check if we got an empty target path
            if (string.IsNullOrEmpty(targetFullpath))
            {
                targetFullpath = GetDefaultFilenameForFile(fullPath);
            }

            // prepend original directory if the path given is not absolute
            if (!Path.IsPathRooted(targetFullpath))
                targetFullpath = Path.Combine(Path.GetDirectoryName(fullPath), targetFullpath);
        }

        private bool TryMoveFile(string fullPath, string fullDestPath)
        {
            try
            {
                File.Move(fullPath, fullDestPath);
                LogTrace("Renamed '{0}' to '{1}'", fullPath, fullDestPath);
                return true;
            }
            catch (IOException)
            {
                /* file may be in use by hl/hl2 when still recording */
            }
            return false;
        }

        private void CompressAndMoveReplayAsync(string replayPath, int matchId)
        {
            ThreadPool.QueueUserWorkItem(new WaitCallback(CompressThreadWorker), new CompressionThreadState(replayPath, matchId));
        }

        private class CompressionThreadState
        {
            public CompressionThreadState(string source, int matchId)
            {
                SourcePath = source;
                MatchID = matchId;
            }
            public string SourcePath { get; private set; }
            public int MatchID { get; private set; }
        }

        private void CompressThreadWorker(object state)
        {
            ThreadPriority oldPriority = Thread.CurrentThread.Priority;
            try
            {
                Thread.CurrentThread.Priority = ThreadPriority.BelowNormal;
                CompressionThreadState c = state as CompressionThreadState;
                string result = Utils.CompressFile(c.SourcePath);
                gameInterface.moveToMatchMedia(result, c.MatchID);
                // delete original replay
                File.Delete(c.SourcePath);
            }
            catch (Exception ex)
            {
                Trace.WriteLine(ex.ToString());
            }
            finally
            {
                Thread.CurrentThread.Priority = oldPriority;
            }
        }

        private void ReplayWatcher_Changed(object sender, FileSystemEventArgs e)
        {
            LogDebug("File event for '{0}'", e.FullPath);
            if (!isReplayRecording)
            {
                // set this as early as possible
                isReplayRecording = true;
                // status has changed, notify listeners
                OnFileWritten(e.FullPath);
            }

            //if (AddFileToList(gatheredFiles, e.FullPath))
            if (recentFile == null || recentFile.FullName != e.FullPath)
                recentFile = new FileInfo(e.FullPath);

            if (Settings.Default.OverwriteProtection)
            {
                try
                {
                    if (TryMoveFile(e.FullPath, targetFullpath))
                    {
                        if (Settings.Default.MoveToMatchmedia && matchId != 0)
                        {
                            CompressAndMoveReplayAsync(targetFullpath, matchId);
                        }

                        recentFile = null;

                        gameInterface.showInGameNotification(
                            Resources.RecordRedButton,
                            Resources.OsdRecordingCompleteTitle,
                            string.Format(Resources.OsdRecordingCompleteText, Path.GetFileName(e.FullPath)),
                            5000);
                    }
                }
                catch (Exception ex)
                {
                    Trace.WriteLine(ex.ToString());
                }
            }
        }
    }
}
